home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Magnum One
/
Magnum One (Mid-American Digital) (Disc Manufacturing).iso
/
d12
/
cptutor1.arc
/
CHAP04.TXT
< prev
next >
Wrap
Text File
|
1990-07-20
|
22KB
|
473 lines
Chapter 4
FUNCTIONS
This chapter discusses the changes in the use of functions that
have been made to C++ in order to make programming more convenient
and to let the compiler do further checking for errors. A fair
amount of time is also spent on teaching the modern form of
function definition and prototyping.
Prototyping allows the compiler to do additional type checking for
your function calls which can aid in detecting programming errors.
The first two example programs in this chapter are designed to
teach prototyping and what it will do for you. Prototyping is a
relatively new addition to C, so even some experienced C
programmers are not familiar with it. If you have experience with
prototyping you can skip directly to the section named INLINE CODE
on page 4-4 of this chapter.
PROTOTYPES
_________________________________________________________________
Examine the file named PROTYPE1.CPP for our ================
first look at a prototype and an illustration of PROTYPE1.CPP
how it is used. The prototyping used in C++ is ================
no different than that used in ANSI-C.
Actually, many C programmers take a rather dim
view of prototyping and seem reluctant to use it, but with C++ it
is considerably more important and is in much heavier use. In
fact, prototyping is required to be used in C++.
A prototype is a limited model of a more complete entity to come
later. In this case, the full function is the complete entity to
come later and the prototype is illustrated in line 4. The
prototype gives a model of the interface to the function that can
be used to check the calls to the function for the proper number
of parameters and the correct types of parameters. Each call to
the function named do_stuff() must have exactly three parameters
or the compiler will give an error message. In addition to the
correct number of parameters, the types must be compatible or the
compiler will issue an error message. Notice that when the
compiler is working on lines 12 and 13, the type checking can be
done based on the prototype in line 4 even though the function
itself is not yet defined. If the prototype is not given, the
number of parameters will not be checked, nor will the types of the
parameters be checked. You will get an apparently good compile and
link, but the program may do some very strange things when it is
executed if the correct number of parameters are not used.
To write the prototype, simply copy the header from the function
to the beginning of the program and append a semicolon to the end
as a signal to the compiler that this is not a function but a
prototype. The variable names given in the prototype are optional
Page 4-1
Chapter 4 - Functions
and act merely as comments to the program reader since they are
completely ignored by the compiler. You could replace the variable
name wings in line 4 with your first name and there would be no
difference in compilation. Of course, the next person that had to
read your program would be somewhat baffled with your choice of
names.
In this case, the two function calls to this function, given in
lines 12 and 13, are correct so no error will be listed during
compilation.
Even though we wish to use the char type for eyes in the function,
we wish to use it as a number rather than as a character. The cast
to int in line 20 is required to force the printout of the
numerical value rather than an ASCII character. The next example
program is similar but without the cast to int.
COMPATIBLE TYPES
_________________________________________________________________
We mentioned compatible types earlier so we should review them just
a bit in order to make our discussion of prototyping complete.
Compatible types are any simple types that can be converted from
one to another in a meaningful way. For example, if you used an
integer as the actual parameter and the function was expecting a
float type as the formal parameter, the system would do the
conversion automatically, without mentioning it to you. This is
also true of a float changing to a char, or a char changing to an
int. There are definite conversion rules which would be followed.
These rules are given in great detail in section 3.2 of the draft
of the ANSI-C standard and are also given on page 198 of the second
edition of the K&R reference.
If we supplied a pointer to an integer as the actual parameter and
expected an integer as the formal parameter in the function, the
conversion would not be made because they are two entirely
different kinds of values. Likewise, a structure would not be
converted automatically to a long float, an array, or even to a
different kind of structure, they are all incompatible and cannot
be converted in any meaningful manner. The entire issue of type
compatibility as discussed in chapter 2 of this tutorial applies
equally well to the compatibility of types when calling a function.
Likewise, the type specified as the return type, in this case void,
must be compatible with the expected return type in the calling
statement, or the compiler will issue a warning.
HOW DOES PROTOTYPING WORK?
_________________________________________________________________
This is your chance to try prototyping for yourself and see how
well it works and what kinds of error messages you get when you do
certain wrong things. Change the actual parameters in line 12 to
Page 4-2
Chapter 4 - Functions
read (12.2, 13, 12345) and see what the compiler says about that
change. It will probably say nothing because they are all type
compatible. If you change it to read (12.0, 13), it will issue a
warning or error because there are not enough arguments given.
Likewise you should receive an error message if you change one of
the parameters in line 13 to an address by putting an ampersand in
front of one of the variable names. Finally, change the first word
in line 4 from void to int and see what kind of error message is
given. You will first be required to make the function header in
line 16 agree with the prototype, then you will find that there is
not a variable returned from the function. Finally, you will find
that there is a returned value that is not used in the calling
program. You should have a good feeling that prototyping is doing
something good for you after making all of those changes.
Be sure to compile and execute this program then make the changes
recommended above, attempting to compile it after each change.
A LITTLE MORE PROTOTYPING
_________________________________________________________________
Examine the next example program named ================
PROTYPE2.CPP for a little more information on PROTYPE2.CPP
prototyping. This program is identical to the ================
last one except for a few small changes. The
variable names have been omitted from the
prototype in line 4 merely as an illustration that they are
interpreted as comments by the C++ compiler. The function header
is formatted differently to allow for a comment alongside each of
the actual parameters. This should make the function header a
little more self explanatory. However, you should remember that
comments should not be used to replace good selection of variable
names. In this particular case, the comments add essentially
nothing to the clarity of the program.
WHAT DOES PROTOTYPING COST?
_________________________________________________________________
Prototyping is essentially free because it costs absolutely nothing
concerning the run time size or speed of execution. Prototyping
is a compile time check and slows down the compile time a
negligible amount because of the extra checking that the compiler
must do. If prototyping finds one error for you that you would
have had to find with a debugger, it has more than paid for itself
for use in an entire project. I once spent 12 hours of debugging
time to find that I forgot to pass the address of a variable to a
function. Prototyping would have found the error on the first
compilation of this 2000 line program.
The only price you pay to use prototyping is the extra size of the
source files because of the prototypes, and the extra time for the
Page 4-3
Chapter 4 - Functions
compiler to read the prototypes during the compilation process, but
both costs are negligible.
Be sure to compile and execute this example program. You will find
that it is identical to the last example program.
PASS BY REFERENCE
_________________________________________________________________
Examine The file named PASSREF.CPP for an ===============
example of a pass by reference, a construct PASSREF.CPP
which is not available in ANSI-C. The reference ===============
variable was mentioned in chapter 1 and it was
recommended there that you don't use it. This
example program illustrates where it can be used to your advantage.
The pass by reference allows the passing of a variable to a
function and returning the changes made in the function to the main
program. In ANSI-C the same effect can be seen when a pointer to
a variable is passed to a function, but this is a little neater.
Observe the prototype in line 4 where the second variable has an
ampersand in front of the variable name. The ampersand instructs
the compiler to treat this variable just like it were passed a
pointer to the variable since the actual variable from the main
program will be used in the function. In the function itself, in
lines 21 through 24, the variable in2 is used just like any other
variable but we are using the variable passed to this function from
the main program not a copy of it. The other variable named in1
is treated just like any other normal variable in ANSI-C. In
effect, the name in2 is a synonym for the variable named index in
the main program.
If you prefer to omit the variable names in the prototypes, you
would write the prototype as follows;
void fiddle(int, int&);
If you are a Pascal programmer, you will recognize that the
variable named in1 is treated just like a normal parameter in a
Pascal call, a call by value. The variable named in2 however, is
treatedlike a variable with the reserved word VAR used in front of
it usually referred to as a call by reference.
When you compile and execute this program, you will find that the
first variable got changed in the function but was returned to its
original value when we returned to the main program. The second
variable however, was changed in the function and the new value was
reflected back into the variable in the main program which we can
see when the values are listed on the monitor.
Page 4-4
Chapter 4 - Functions
DEFAULT PARAMETERS
_________________________________________________________________
Examine the file named DEFAULT.CPP for an ===============
example of the use of default parameters in C++. DEFAULT.CPP
This program really looks strange since it ===============
contains default values for some of the
parameters in the prototype, but these default
values are very useful as we will see shortly.
This prototype says that the first parameter named length must be
given for each call of this function because a default value is not
supplied. The second parameter named width, however, is not
required to be specified for each call, and if it is not specified,
the value 2 will be used for the variable width within the
function. Likewise, the third parameter is optional, and if it is
not specified, the value of 3 will be used for height within the
function.
In line 11 of this program, all three parameters are specified so
there is nothing unusual about this call from any other function
call we have made. Only two values are specified in line 12
however, so we will use the default value for the third parameter
and the system acts as if we called it with (x, y, 3) since the
default value for the third value is 3. In line 13, we only
specified one parameter which will be used for the first formal
parameter, and the other two will be defaulted. The system will
act as if we had called the function with (x, 2, 3). Note that the
output from these three lines is reversed. This will be explained
shortly.
There are a few rules which should be obvious but will be stated
anyway. Once a parameter is given a default value in the list of
formal parameters, all of the remaining must have default values
also. It is not possible to leave a hole in the middle of the
list, only the trailing values can be defaulted. Of course, the
defaulted values must be of the correct types or a compiler error
will be issued. The default values can be given in either the
prototype or the function header, but not in both. If they are
given in both places, the compiler must not only use the default
value, but it must carefully check to see that both values are
identical. THis could further complicate an already very
complicated problem, that of writing a C++ compiler.
WHY IS THE OUTPUT SCRAMBLED?
_________________________________________________________________
When the compiler finds a cout statement, the complete line of code
is initially scanned from right to left to evaluate any functions,
then the data is output field by field from left to right.
Therefore in line 11, get_value() is evaluated with its internal
output displayed first. Then the fields of the cout are displayed
from left to right with "Some box data is" displayed next.
Finally, the result of the return from get_value() is output in int
Page 4-5
Chapter 4 - Functions
format, the type of the returned value. The end result is that the
output is not in the expected order when lines 11 through 13 are
executed. (The output is not what you would intuitively expect to
happen so appears to be a deficiency in the language. A call to
Borland International, the writers of TURBO C++, verified that this
is operating correctly.)
Lines 15 through 18 are similar to any two of the lines of code in
lines 11 through 13, but are each separated into two lines so the
output is correct.
Be sure to compile and execute DEFAULT.CPP after you understand it.
Note that the funny output will appear again later in this
tutorial.
VARIABLE NUMBER OF ARGUMENTS
_________________________________________________________________
Examine the program named VARARGS.CPP for an ===============
illustration of the use of a variable number of VARARGS.CPP
arguments in a function call. ===============
We have gone to a lot of trouble to get the
compiler to help us by carefully checking how many parameters we
use in the function calls and checking the types of the parameters.
On rare occasion, we may wish to write a function that uses a
variable number of parameters. The printf() function is a good
example of this. ANSI-C has a series of three macros available in
the "stdarg.h" header file to allow the use of a variable number
of arguments. Of course these are available for use with C++ also,
but we need a way to eliminate the strong type checking that is
done with all C++ functions. The three dots illustrated in line
6 will do this for us. This prototype says that a single argument
of type int is required as the first parameter, then no further
checking will be done by the compiler.
You will note that the main program consists of three calls to the
function, each with a different number of parameters, and the
system does not balk at the differences in the function calls. In
fact, you could put as many different types as you desire in the
calls. As long as the first one is an int type variable, the
system will do its best to compile and run it for you. Of course
the compiler is ignoring all type checking beyond the first
parameter so it is up to you to make sure you use the correct
parameter types in this call.
In this case the first parameter gives the system the number of
additional parameters to look for and handle. In this simple
program, we simply display the numbers on the monitor to illustrate
that they really did get handled properly.
Of course, you realize that using a variable number of arguments
in a function call can lead to very obscure code and should be used
Page 4-6
Chapter 4 - Functions
very little in a production program, but the capability exists if
you need it. Be sure to compile and execute this program.
FUNCTION NAME OVERLOADING
_________________________________________________________________
Examine the file named OVERLOAD.CPP for an ================
example of a program with the function names OVERLOAD.CPP
overloaded. This is not possible in ANSI-C, but ================
is perfectly legal and in fact used quite
regularly in C++. At first this will seem a bit
strange, but it is one of the keystones of object oriented
programming. You will see its utility and purpose very clearly in
later chapters of this tutorial.
You will notice in this example program that there are three
functions, in addition to the main function, and all three have the
same name. Your first question is likely to be, "Which function
do you call when you call do_stuff()?" That is a valid question
and the answer is, the function that has the correct number of
formal parameters of the correct types. If do_stuff() is called
with an integer value or variable as its actual parameter, the
function beginning in line 22 will be called and executed. If the
single actual parameter is of type float, the function beginning
in line 27 will be called, and if two floats are specified, the
function beginning in line 33 will be called.
It should be noted that the return type can also be used to
determine which function will be called, but that is of no help in
this case because the stream output functions called in lines 15
through 19 are typeless as mentioned earlier.
The keyword overload used in line 4 tells the system that you
really do intend to overload the name do_stuff, and the overloading
is not merely an oversight. This is only required in C++ version
1.2. C++ version 2.0 and greater do not require the keyword
overload but allows it to be used optionally in order to allow the
existing body of C++ code to be compilable with newer compilers.
It may be best to include the keyword as an indication to future
readers of your code that function name overloading is
intentionally used here.
The actual selection is done at compile time, not at execution time
so the program is not slowed down. If each of the overloaded
function names were changed to different names, each being unique,
there would be no difference in execution size or time of the
resulting program.
Overloading of function names may seem very strange to you , and
it is strange if you are used to the rules of K&R or ANSI-C
programming. As you gain experience with C++, you will feel very
comfortable with this and you will use it a lot in your C++
programming.
Page 4-7
Chapter 4 - Functions
Note the use of the keyword const used in some of the function
prototypes and headers. Once again, this prevents the programmer
from accidentally changing the formal parameter within the
function. In a function as short as these, there is no real
problem with an accidental assignment. In a real function that you
occasionally modify, you could easily forget the original intention
of the use of a value and attempt to change it during an extended
debugging session.
PROGRAMMING EXERCISES
_________________________________________________________________
1. Change the type of wings in the prototype of PROTYPE1.CPP to
float so that it disagrees with the function definition to see
if you get a compilation error.
2. Change the function definition in PROTYPE1.CPP to agree with
the changed prototype. Compile and execute the program
without changing the calls in lines 12 and 13. Explain the
results.
3. In DEFAULT.CPP, remove the default value from the prototype
for height only to see what kind of compiler error you get.
Only the last values of the list can be defaulted.
4. In OVERLOAD.CPP, change the names of the three functions so
that each is a unique name and compare the size of the
resulting executable file with that given by the present
program.
Page 4-8